<!--
Copyright (C) 2024 Richard Hao Cao
Ebrowser is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Ebrowser is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>
  html{
    height: 100%;
    overflow: hidden;
  }
  body{
    display: flex;
    flex-direction: column;
    height: 100%;
    margin-top: 1px;
  }
  div.webviews{
    display: flex;
    flex-direction: column;
    flex-grow:1;
  }
  webview{display: none;width:100%;height:100%}
  .curWV{display: inherit !important;}
  .autocomplete-active {
    background-color: DodgerBlue !important; 
    color: #ffffff; 
  }
  .invis{display: none}
  /*the container must be positioned relative:*/
  .autocomplete {
    position: relative;
    display: inline-block;
    width:100%;
  }
  .autocomplete-items {
    position: absolute;
    border: 1px solid #d4d4d4;
    border-bottom: none;
    border-top: none;
    z-index: 99;
    /*position the autocomplete items to be the same width as the container:*/
    top: 100%;
    left: 0;
    right: 0;
  }
  .autocomplete-items div {
    cursor: pointer;
    background-color: #fff;
  }
  .autocomplete-items div:hover {
    background-color: #e9e9e9;
  }
</style>
<script>
  const { ipcRenderer } = require('electron');
  const fs = require('fs');
  const path = require('path');
  const readline = require('readline');
  const cssInvert = "html,img,video{filter:invert(1) hue-rotate(180deg) !important;}body{background:white !important;}img[src],input[type=image]{opacity:.9;}";
  var iTab = 0;
  var tabs;
  var engines = {};
  var mapKeys = {};
  var closedUrls = [];
  var autocStrArray = [];
  var defaultSE = "https://www.bing.com/search?q=%s";
  var historyFile = path.join(__dirname,'history.rec');
  var bHistory = false;
  var bQueryHistory = false;
  const currentHour = new Date().getHours();
  var bDarkTheme = (currentHour >= 17 || currentHour < 8);
  let sitecssP = path.join(__dirname,"sitecss");
  let sitejsP = path.join(__dirname,"sitejs");
  let gjsA = [];
  let gcssA = [];
  let gcssJSA = [];
  var bDomainJS = fs.existsSync(sitejsP);
  var bDomainCSS = fs.existsSync(sitecssP);
  var autocMode = 0; //0 for substring, 1 for startsWith
  const BML_head = "(async ()=>{let d=document;async function _loadJs(u){var a=d.createElement('script');a.type='text/javascript';a.async=false;a.src=u;d.body.appendChild(a);await new Promise(resolve=>a.onload=resolve)}";
  const BML_tail = "})()";
  const BML_md = BML_head + "await _loadJs('https://cdn.jsdelivr.net/npm/marked@15.0.6/marked.min.js');let b=d.body;b.innerHTML=marked.parse(b.textContent)})()";

  let lastKeys;
  let lastKeys_millis = 0;
  var lastVal;
  fs.readFile(path.join(__dirname,'search.json'), 'utf8', (err, jsonString) => {
    if (err) {
      coloncommand(":js fetch2file(repositoryurl,'search.json')");
      return;
    }
    initSearchEngines(jsonString,false);
  });
  fs.readFile(path.join(__dirname,'mapkeys.json'), 'utf8', (err, jsonStr) => {
    if (err) {
      coloncommand(":js fetch2file(repositoryurl,'mapkeys.json')");
      return;
    }
    try {
      mapKeys = JSON.parse(jsonStr);
    }catch(e){}
  });
  appendAutoc_rec(path.join(__dirname,'default.autoc'),null);
  appendAutoc_rec(path.join(__dirname,'bookmark.rec'),' ');

  function initSearchEngines(jsonStr){
    try{
      engines=JSON.parse(jsonStr);
      let match = jsonStr.match(/:"([^"]+)"/);
      if(match) defaultSE=match[1];
    }catch(e){}
  }
  function save(filePath, u8array){
    //alert(Object.prototype.toString.call(u8array))
    fs.writeFile (filePath, u8array, (err) => {
      if (err) {
        console.error (err);
        return;
      }
    });
  }
  function print2PDF(filePath, options){
    tabs.children[iTab].printToPDF(options)
      .then(u8array=>save(filePath,u8array));
  }
  function showHtml(html){
    let js = "document.documentElement.innerHTML=`"+html+"`;";
    let t=tabs.children[iTab];
    t.dataset.jsonce=js;
    t.src="about:blank";
  }
  function bookmark(argrest){//b [filenamestem] :bookmark
    let bmFileName = "bookmark.rec";
    let tab = tabs.children[iTab];
    let url = tab.getURL();
    if(argrest)
      bmFileName = argrest+".rec";
    let title = tab.getTitle();
    let line = title + " " + url + "\n";
    fs.appendFile(path.join(__dirname,bmFileName), line, (err)=>{});
  }
  function switchTab(i){
    let tab = tabs.children[iTab];
    if(document.activeElement == tab) tab.blur();
    tab.classList.remove('curWV');
    iTab = i;
    tabs.children[iTab].classList.add('curWV');
  }
  async function loadJSFile(tab,jsF){
      try {
        let js = await fs.promises.readFile(jsF,'utf8');
        tab.executeJavaScript(js,false);
      }catch(e){}
  }
  async function loadCSSFile(tab,jsF){
      try {
        let js = await fs.promises.readFile(jsF,'utf8');
        tab.insertCSS(js);
      }catch(e){}
  }
  function cbStartLoading(e){
    let tab = e.target;
    if(bDarkTheme) tab.insertCSS(cssInvert);
    if(!bDomainCSS) return;
    let domain;
    try{
      domain = new URL(tab.getURL()).hostname;
    }catch(e){return;}
    let jsF = path.join(sitecssP, domain+".js");
    loadJSFile(tab,jsF);
    jsF = path.join(sitecssP, domain+".css");
    loadCSSFile(tab,jsF);
    gcssA.forEach((fname)=>{
      jsF = path.join(__dirname, fname);
      loadCSSFile(tab,jsF);
    });
    gcssJSA.forEach((fname)=>{
      jsF = path.join(__dirname, fname);
      loadJSFile(tab,jsF);
    });
  }
  function cbNavigate(e){
    let url = e.url;
    if(58===url.charCodeAt(1)){
      e.preventDefault();
      if(confirm("Proceed to execute risky operations: "+url))
        internalLink(url);
      else
        document.forms[0].q.value = url;
    }
  }
  function cbFinishLoad(e){
    let tab = e.target;
    let url = tab.getURL();
    if(bHistory){
      let histItem = tab.getTitle()+" "+url+"\n";
      fs.appendFile(historyFile, histItem, (err) => {});
    }
    let js = tab.dataset.jsonce;
    if(js){
      tab.dataset.jsonce = null;
      tab.executeJavaScript(js,false);
    }
    if(bDomainJS){
      let domain = new URL(url).hostname;
      let jsF = path.join(sitejsP, domain+".js");
      loadJSFile(tab,jsF);
    }
    gjsA.forEach((fname)=>{
      let jsF = path.join(__dirname, fname);
      loadJSFile(tab,jsF);
    });
  }
  function initTab(tab){
    tab.allowpopups = true;
    tab.addEventListener('did-finish-load',cbFinishLoad);
    tab.addEventListener('dom-ready',cbStartLoading);
    tab.addEventListener('will-navigate',cbNavigate);
  }
  function newTab(){
    var tab = document.createElement('webview');
    initTab(tab);
    tabs.appendChild(tab);
  }
  function tabInc(num){
    let nTabs = tabs.children.length;
    if(nTabs<2) return;
    let i = iTab +num;
    if(i>=nTabs) i=0;
    switchTab(i);
  }
  function tabDec(num){
    let nTabs = tabs.children.length;
    if(nTabs<2) return;
    let i = iTab +num;
    if(i<0) i=nTabs-1;
    switchTab(i);
  }
  function tabClose(){
    let nTabs = tabs.children.length;
    if(nTabs<2) return "";//no remain tab
    let tab = tabs.children[iTab];
    closedUrls.push(tab.getURL());
    if(document.activeElement == tab) tab.blur();
    tabs.removeChild(tab);
    nTabs--;
    if(iTab>=nTabs) iTab=iTab-1;
    tabs.children[iTab].classList.add('curWV');
    return getWinTitle();
  }
  function tabJS(js){
    tabs.children[iTab].executeJavaScript(js,false);
  }
  function getWinTitle(){
    let t=tabs.children[iTab];
    let title = (iTab+1) + '/' + tabs.children.length;
    try{title=title+' '+t.getTitle()+' '+t.getURL()}catch(e){}
    return title
  }
  async function appendAutoc_rec(filename, delimit){
    try{
      const readInterface = readline.createInterface ({
        input: fs.createReadStream (filename, 'utf8'),
      });

      for await (const line of readInterface) {
        let iS;
        if(delimit && (iS=line.lastIndexOf(delimit))>0){
          autocStrArray.push(line.substring(iS+1));
        }else
           autocStrArray.push(line);
      }
      lastVal = null; //trigger full search
    }catch(e){return;}
  }
  function keyPress(e){
    var inputE = document.forms[0].q;
    if (e.altKey||e.metaKey)
      return;
    var key = e.key;
    if(e.ctrlKey){
      switch(key){
      case "Home":
        tabJS("window.scrollTo(0,0)");
        return;
      case "End":
        tabJS("window.scrollTo(0,document.body.scrollHeight)");
        return;

      }
      return;
    }
    SCROLL: do {
    let h = -32;
    switch(key){
    case " ":
      if(inputE === document.activeElement) return;
      if(e.shiftKey){
        h = -3*document.documentElement.clientHeight/4;
        break;
      }
    case "PageDown":
      h = 3*document.documentElement.clientHeight/4;
      break;
    case "PageUp":
      h = -3*document.documentElement.clientHeight/4;
      break;
    case "ArrowDown":
      h = 32;
    case "ArrowUp":
      if(inputE === document.activeElement &&
         0!==inputE.nextElementSibling.children.length)
        return; 
      break;
    default:
      break SCROLL;
    }
    let js = `javascript:window.scrollBy(0,${h})`;
    tabJS(js);
    return;
    }while(false);

    if(inputE === document.activeElement){
      if (9===e.keyCode){
        e.preventDefault();
        tabs.children[iTab].focus();
      }
      return;
    }
    var curMillis = Date.now();
    if(curMillis-lastKeys_millis>1000)
      lastKeys = null;
    lastKeys_millis = curMillis;

    switch (key) {
    case "!":
    case "/":
    case ":":
      inputE.value = "";
      inputE.focus();
      lastKeys = null;
      return;
    }
    lastKeys = !lastKeys ? key : lastKeys + key;
    let cmds = mapKeys[lastKeys];
    if(cmds){//try to run cmds
      let keyLen = lastKeys.length;
      setTimeout(()=>{
        if(lastKeys.length != keyLen) return;
        lastKeys = null;
        handleQueries(cmds);
      }, 500);
    }
  }
  function getQ(){return document.forms[0].q.value;}
  function bang(query, iSpace){
    let se=defaultSE;
    {
      let name = query.slice(0,iSpace);
      let engine = engines[name];
      if(engine){
        se = engine;
        query = query.substring(iSpace+1);
      }
    }
    return se.replace('%s',query);
  }
  function coloncommand(q){
    ipcRenderer.send("command",q);
  }
  function coloncommand_render(cmd){
    const delimiter = /\s+/;
    let arg0;
    let argrest = null;
    const match = delimiter.exec(cmd);
    if(match){
      const sIndex = match.index;
      const eIndex = sIndex + match[0].length;
      arg0 = cmd.substring(1,sIndex);
      argrest = cmd.substring(eIndex);
    }else
      arg0 = cmd.substring(1);
    switch(arg0){
    case "ac":
      autoc(argrest);
      return;
    case "b":
      bookmark(argrest);
      return;
    case "bjs":
      eval(argrest);
      return;
    case "bml":
      bangcommand(argrest,0);
      return;
    case "pdf":
      savePdf(argrest);
      return;
    }

  }
  function autoc(argrest){
    if(!argrest) return;
    let fpath = path.join(__dirname,argrest);
    let fname = fpath;
    let delimit = ' ';
    if (!fs.existsSync(fname)){
      fname = fpath+".autoc";
      if (!fs.existsSync(fname))
        fname = fpath+".rec";
      else
        delimit = null;
    }
    appendAutoc_rec(fname,delimit);
  }
  function bml(args){
    if(2!=args.length) return;
    let filename = args[1]+".js";
    fs.readFile(path.join(__dirname,filename), 'utf8', (err,str) => {
      if (err) return;
      tabs.children[iTab].executeJavaScript(str,false);
    });
  }
  function savePdf(argrest){
    let filename = "ebrowser.pdf";
    let options = {};
    if(argrest){
      let c0 = argrest.charCodeAt(0);
      let iOpts = 0;
      if(123!=c0){//not '{' options then it is filename
        let iS = argrest.indexOf(' ');
        let filestem = argrest;
        if(iS>0) {
          filestem = argrest.substring(0,iS);
          iOpts = iS+1;
        }else
          iOpts = argrest.length;
        filename = filestem + ".pdf";
      }
      if(argrest.length>iOpts){//:Pdf [filename] {...}
        if(iOpts+2==argrest.length){// '{}'
          let width = document.body.clientWidth/96;
          tabs.children[iTab].executeJavaScript("document.documentElement.scrollHeight",
          false).then((h)=>{
            let opts = {
              printBackground:true,
              pageSize:{width:width,height:h/96}};
            print2PDF(filename,opts);
          });
          return;
        }else{
          try {
            options = JSON.parse(argrest.substring(iOpts));
          }catch(e){};
        }
      }
    }
    print2PDF(filename,options);
  }
  function bangcommand(q,offset){
    let iS = q.indexOf(' ',offset);
    if(iS<0) iS=q.length;
    let fname = q.substring(offset,iS);
    let fpath = path.join(__dirname,fname+'.js');
    if (fs.existsSync(fpath)) {
      fs.readFile(fpath, 'utf8',(err, js)=>{
        if (err) {
          console.log(err);
          return;
        }
        const prefix = "(function(){";
        const postfix = "})(`";
        const end ="`)";
        const fjs = `${prefix}${js}${postfix}${q}${end}`;
        tabs.children[iTab].executeJavaScript(fjs,false);
      });
    }
  }
  function recQueryHistory(q){
    if(bQueryHistory)
      fs.appendFile(path.join(__dirname,"history.autoc"), q, (err)=>{});
  }
  function handleQuery(q){
    if(q.length>1){
      let c0=q.charCodeAt(0);
      let c1=q.charCodeAt(1);
      switch(c0){
      case 33://"!"
        if(33!=c1)
          coloncommand(q);
        else
          bangcommand(q,2);
        recQueryHistory(q);
        return;
      case 47://"/"
        tabs.children[iTab].findInPage(q.substring(1));
        return;
      case 58://':'
        if(c1>98 && 112!=c1)
          coloncommand(q);
        else
          coloncommand_render(q);
        recQueryHistory(q);
        return;
      }
      if(58===c1){
        internalLink(q);
        return;
      }
    }
    var url=q;
    NOREC: do{
    do {
      if(q.length>12){
        let c6 = q.charCodeAt(6);
        if(47===c6){// '/'
          let c5 = q.charCodeAt(5);
          if(47===c5 && 58===q.charCodeAt(4))//http/file urls
            break NOREC;
          if(58===c5 && 47===q.charCodeAt(7))//https://
            break NOREC;
        }else if(q.startsWith("javascript:")){
          tabs.children[iTab].executeJavaScript(q.substring(11),false);
          recQueryHistory(q);
          return;
        }else if(q.startsWith("view-source:")) break;
        else if(q.startsWith("data:")) break;
      }
      let iS = q.indexOf(' ');
      if(iS<0){
        if(q.length>5 && 58===q.charCodeAt(5)){// about:
          break;
        }
        if(q.indexOf('.')>0){
          url = 'http://'+q;
          break NOREC;
        }
        url = defaultSE.replace('%s',q);
        break;
      }
      url = bang(q, iS);
      if(58===url.charCodeAt(1)){
        internalLink(url);
        recQueryHistory(q);
        return;
      }
    }while(false);
    recQueryHistory(q);
    }while(false);
    tabs.children[iTab].src=url;
  }
  function internalLink(url){
    let cmd = url.charCodeAt(2);
    let subcmd = url.charCodeAt(3);
    switch(cmd){
    case 48://'0' i:0
    {
      let iColon = url.indexOf(':',5);
      let name = decodeURIComponent(url.slice(4,iColon));
      let rurl = url.substring(iColon+1);
      switch(subcmd){
      case 47://'/' i:0/
        if(106===url.charCodeAt(4) && 115===url.charCodeAt(5) && 47===url.charCodeAt(6)){
          //i:0/js/xx:[url]
          let fname = name;
          let pname = path.join(__dirname,fname);
          if(fs.existsSync(pname)){
            (async ()=>{
              try {
                let js = await fs.promises.readFile(pname,'utf8');
                let t=tabs.children[iTab];
                t.dataset.jsonce=js;
                t.src = rurl;
              }catch(e){}
            })();
          }
        }
        return;
      case 48:  //i:00
      {
        let endS;
        let is = url.indexOf('%s',iColon+1);
        if(is<0)
          endS = '%s"\n';
        else
          endS = '"\n';
        let pname = path.join(__dirname,"search.json");
        let str = '"'+name+'":"'+rurl+
          endS;
        jsonAppend(pname,125,str);
      }
      return;
      case 49: //i:01
      {
        let pname = path.join(__dirname,"menu.json");
        let str = '"'+name+'",":bjs handleQuery(`'+
          rurl+'${tabs.children[iTab].getURL()}`)"\n';
        jsonAppend(pname,93,str);
      }
      return;
      case 50: //i:02
      {
        let pname = path.join(__dirname,"uas.json");
        let str = '"'+name+'":"'+rurl+'"\n';
         jsonAppend(pname,125,str);
      }
      return;
      case 85: {//i:0U
        let sscmd = url.charCodeAt(4);
        switch(sscmd){
        case 65: {//i:0UA
          let iC = url.indexOf(':',5);
          let ua = navigator.userAgent;
          ua += ' '+url.substring(5,iC);
          tabs.children[iTab].setUserAgent(ua);
          handleQuery(url.substring(iC+1));
        }
        return;
        }
      }
      return;
      default:
      }
    }
    return;
    case 56: //i:8
      switch(subcmd){
      case 100: //i:8d[url] to download
        tabs.children[iTab].downloadURL(url.substring(4));
        return;
      }
      return;
    case 112: //i:p[url]#[querystring] for http POST
    {
      let iQ = url.indexOf('#',12);
      let data;
      let rurl;
      let headers;
      switch(subcmd){
      case 49: //i:p1
      {
        if(iQ>4) {
          rurl = url.substring(4,iQ);
          data = [{
            type: 'rawData',
            bytes: Buffer.from(url.substring(iQ+1))
          }];
        }else{
          rurl = url.substring(4);
          data = [];
        }
        headers = 'Content-Type: application/x-www-form-urlencoded';
        break;
      }
      case 50: //i:p2[url]#[filePath] post request with local file
        rurl = url.substring(4,iQ);
        data = [{type: 'file', filePath:url.substring(iQ+1)}];
        headers = 'Content-Type: multipart/form-data';
        break;
      default:
        return;
      }
      tabs.children[iTab].loadURL(rurl,{postData:data,extraHeaders:headers});
      return;
    }
    case 113: //i:q for multiple queries
      handleQueries(url.substring(3));
      return;
    }
  }
  function handleQueries(cmds){
    for(var cmd of cmds.split("\n"))
      handleQuery(cmd);
  }
async function jsonAppend(filePath, charcode, str){
  let fd;
  try{
    fd = await fs.promises.open(filePath, 'r+');
  }catch(e){
    try {
      fd = await fs.promises.open(filePath, 'w+');
    }catch(e1){return}
  }
  try{
    const stats = await fd.stat();
    const fileSize = stats.size;
    const buffer = Buffer.alloc(1);
    let position = fileSize-1;
    while (position >= 0) {
      await fd.read(buffer, 0, 1, position);
      if (buffer[0] === charcode) break;
      position--;
    }
    let endS = String.fromCharCode(charcode);
    if(position<0){//re-write whole file
      str = String.fromCharCode(charcode-2)+str+endS;
      position = 0;
    }else
      str = ","+str+endS;
    await fd.truncate(position);
    const buf = Buffer.from(str);
    await fd.write(buf, 0, buf.length, position);
    await fd.close();
  }catch(e){console.log(e)}
}
function autocomplete(inp,container,arr) {
  var currentFocus;
  function clickItem(e){inp.value = arr[e.target.dataset.index];}
  function appendElement(el,dataindex){
    el.dataset.index = dataindex;
    el.addEventListener("click", clickItem);
    container.appendChild(el);
  }
  function itemInnerHTML(b,str,val,iStr){
    b.innerHTML = str.substr(0,iStr);
    b.innerHTML += "<strong>" + str.substr(iStr, val.length) + "</strong>";
    b.innerHTML += str.substr(iStr+val.length);
  }
  inp.addEventListener("input", function(e) {
    const MAXITEMS = 30;
    var b, i, val = this.value;
    if (!val) { return false;}
    currentFocus = -1;
    let items = container.children;
    let iBase = 0;
    let lastV = lastVal;
    lastVal = val;
    if(val.startsWith(lastV)){
      let itemsLen = items.length;
      if(itemsLen<=0) return;
      i = itemsLen -1;
      iBase = items[i].dataset.index +1;
      switch(autocMode){
      case 0:
        for (; i>=0; i--) {
          b = items[i];
          let str = arr[b.dataset.index];
          let iStr = str.indexOf(val);
          if(iStr<0) {
            b.parentNode.removeChild(b);
            continue;
          }
          itemInnerHTML(b,str,val,iStr);
        }
        break;
      case 1:
        for (; i>=0; i--) {
          b = items[i];
          let str = arr[b.dataset.index];
          let oLen = lastval.length;
          if(!str.startsWith(val.substring(oLen),oLen)) {
            b.parentNode.removeChild(b);
            continue;
          }
          itemInnerHTML(b,str,val,0);
        }
        break;
      }
      if(itemsLen<MAXITEMS)
        return;
      else
        if(container.children.length>=MAXITEMS) return;
    }else
      closeAllLists();
    i = iBase;
    switch(autocMode){
    case 0:
      for (; i < arr.length; i++) {
        let iStr = arr[i].indexOf(val);
        if(iStr<0) continue;
        {
          b = document.createElement("DIV");
          itemInnerHTML(b,arr[i],val,iStr);
          appendElement(b,i);
          if(container.children.length>=MAXITEMS) break;
        }
      }
      return;
    case 1://startsWith
      for (; i < arr.length; i++) {
        if (arr[i].startsWith(val)) {
          b = document.createElement("DIV");
          itemInnerHTML(b,arr[i],val,0);
          appendElement(b,i);
          if(container.children.length>=MAXITEMS) break;
        }
      }
    }
  });
  inp.addEventListener("keydown", function(e) {
      var x = container.getElementsByTagName("div");
      if (0===x.length) return false;
      if (e.keyCode == 40) {//downarrow
        currentFocus++;
        addActive(x);
      } else if (e.keyCode == 38) { //up
        currentFocus--;
        addActive(x);
      } else if (e.keyCode == 13) {
        if (currentFocus > -1) {
          e.preventDefault();
          if (x) x[currentFocus].click();
          currentFocus = -1;
        }
        closeAllLists();
        lastVal = null;
      }
  });
  function addActive(x) {
    removeActive(x);
    if (currentFocus >= x.length) currentFocus = 0;
    if (currentFocus < 0) currentFocus = (x.length - 1);
    x[currentFocus].classList.add("autocomplete-active");
  }
  function removeActive(x) {
    for (var i = 0; i < x.length; i++) {
      x[i].classList.remove("autocomplete-active");
    }
  }
  function closeAllLists() {
    container.innerHTML = '';
  }
  inp.addEventListener("blur", function () {
    setTimeout(()=>container.classList.add("invis"),200);
  });
  inp.addEventListener("focus", function () {
    container.classList.remove("invis");
  });
}
</script>
</head>
<body>
  <form class="autocomplete" autocomplete="off" action="javascript:handleQuery(getQ())">
  <input type="text" name=q style="width:100%" autofocus>
  <div class="autocomplete-items"></div>
  </form>
  <div class="webviews">
  <webview class="curWV" src="about:blank" allowpopups></webview>
  </div>
  <script>
    tabs = document.body.children[1];
    initTab(tabs.children[0]);
    {
      let inp = document.forms[0].q;
      autocomplete(inp,inp.nextElementSibling,autocStrArray);
    }
    document.addEventListener('keydown', keyPress);
  </script>
</body></html>
